import base64
import json
import os
import re

from django import http
from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views import View

from apps.areas.models import Address
from apps.carts.utils import merge_cart_cookie_to_redis
from apps.goods.models import SKU
from apps.users.models import User
from apps.verifications import constants
from meiduo_mall.settings.dev import logger
from apps.users.utils import check_verify_email_token
from utils.response_code import RETCODE
from django_redis import get_redis_connection

# 判断手机号是否重复
from utils.secret import SecretOauth


class MobileCountView(View):

    def get(self, request, mobile):
        # 1. 接收参数
        # 2. 校验参数
        # 3. 去数据库查询，用户，计算个数
        count = User.objects.filter(mobile=mobile).count()
        # 4. 返回相应对象
        return http.JsonResponse({'code': 0, 'errmsg': 'OK', 'count': count})

# 判断用户名是否重复注册
class UsernameCountView(View):

    def get(self, request, username):
        # 1. 接收参数
        # 2. 校验参数
        # 3. 去数据库查询，用户，计算个数
        count = User.objects.filter(username=username).count()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})
# 注册页
class RegisterView(View):
    """用户注册"""
    # 注册页面显示
    def get(self, request):
        """
        提供注册界面
        :param request: 请求对象
        :return: 注册界面
        """
        return render(request, 'register.html')
    # 注册功能提交
    def post(self, request):
       # 请求方式POST
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')

       # 5.是否同意用户协议
        allow = request.POST.get('allow')


        # 判空all()
        if not all([username,password,password2,mobile]):
            return http.HttpResponseForbidden('参数不齐')

        # 校验参数正则校验re.match
        # 1.用户名 正则
        if not re.match('^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden('请输入5-20个字符的用户名')
        # 2. 密码正则
        if not re.match('^[0-9A-Za-z]{8,20}$', password):
            return http.HttpResponseForbidden('请输入8-20密码')
        # 4. 密码是否一致
        if password != password2:
            return http.HttpResponseForbidden('两次密码不一致')
        # 3.手机号正则
        if not re.match('^1[345789]\d{9}$', mobile):
            return http.HttpResponseForbidden('您输入的手机号格式不正确')
        # 5. 是否勾选一致
        if allow != 'on':
            return http.HttpResponseForbidden('请勾选同意')

        # 短信验证码
        sms_code = request.POST.get('msg_code')

        # 从redis里面取出短信验证码
        redis_code_client = get_redis_connection('sms_code')
        redis_code = redis_code_client.get("sms_%s" % mobile)
       # 和前端短信验证码对比

        if redis_code is None:
            return render(request, 'register.html', {'sms_code_errmsg': '无效的短信验证码'})

        if sms_code != redis_code.decode():
            return render(request, 'register.html', {'sms_code_errmsg': '输入短信验证码有误'})
        # 可以扩展  （判空代表短信失效了，和删除短信验证码后台）
        # 6 注册用户 ORM原生的写法-- create() save()
        # django权限认证 --- create_user
        from apps.users.models import User
        user = User.objects.create_user(username=username, password=password, mobile=mobile)
        # 7. 保持登录状态 :原生--cookie session ; request.session['username']=usename
        from django.contrib.auth import login
        login(request, user)
        # 8. 跳转到 首页 redirect(reverse())
        # return redirect('/')
        return redirect(reverse('contents:index'))
# 登录页
class LoginView(View):

    def get(self, request):
        """
        提供登录界面
        :param request: 请求对象
        :return: 登录界面
        """
        return render(request, 'login.html')

    def post(self, request):
        """
        实现登录逻辑
        :param request: 请求对象
        :return: 登录结果
        """
        # 1.接收三个参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        remembered = request.POST.get('remembered')

        # 2.校验参数
        if not all([username, password]):
            return http.HttpResponseForbidden('参数不齐全')
        # 2.1 用户名
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden('请输入5-20个字符的用户名')
        # 2.2 密码
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return http.HttpResponseForbidden('请输入8-20位的密码')

        # 3.验证用户名和密码--django自带的认证
        from django.contrib.auth import authenticate, login
        user = authenticate(username=username, password=password)

        if user is None:
            return render(request, 'login.html', {'account_errmsg': '用户名或密码错误'})

        # 4.保持登录状态
        login(request, user)

        # 5.是否记住用户名
        if remembered != 'on':
            # 不记住用户名, 浏览器结束会话就过期
            request.session.set_expiry(0)
        else:
            # 记住用户名, 浏览器会话保持两周
            request.session.set_expiry(None)

        next = request.GET.get('next')
        if next:
            response = redirect(reverse('users:info'))
        else:
            response = redirect(reverse('contents:index'))
        response.set_cookie('username', user.username, max_age=3600 * 24 * 14)
        # 等登录成功之后合并购物车数据
        merge_cart_cookie_to_redis(request=request, response=response)

        # 6.返回响应结果
        return response
# 退出登录
class LogoutView(View):
    """退出登录"""

    def get(self, request):
        from django.contrib.auth import logout
        """实现退出登录逻辑"""
        # 清理session
        logout(request)
        # 退出登录，重定向到登录页
        response = redirect(reverse('contents:index'))
        # 退出登录时清除cookie中的username
        response.delete_cookie('username')

        return response
# 用户中心显示

class UserInfoView(LoginRequiredMixin, View):
    """用户中心"""

    def get(self, request):
        """提供个人信息界面"""
        # render是前后端不分离 ---jinja2模板
        # JsonResponse是前后端分离-----vue模板
        # render是前后端不分离 ---jinja2模板-----vue渲染
        context = {
            'username': request.user.username,
            'mobile': request.user.mobile,
            'email': request.user.email,
            'email_active': request.user.email_active
        }
        return render(request, 'user_center_info.html', context=context)

# 新增邮箱
class EmailView(LoginRequiredMixin, View):
    """添加邮箱"""

    def put(self, request):
        """实现添加邮箱逻辑"""
        # 接收参数

        email = json.loads(request.body.decode()).get('email')

        # 校验参数
        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
            return http.HttpResponseForbidden('参数email有误')

        # 修改User.objects.filter().update(email=email)
        try:
            request.user.email = email
            request.user.save()

        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '添加邮箱失败'})
        # 生成激活链接
        from apps.users.utils import generate_verify_email_url
        verify_url = generate_verify_email_url(request.user)
        # 发送邮件
        from celery_tasks.email.tasks import send_verify_email
        send_verify_email.delay(email, verify_url)

        # 响应添加邮箱结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加邮箱成功'})


# 激活邮箱
class VerifyEmailView(View):
    """验证邮箱"""

    def get(self, request):
        """实现邮箱验证逻辑"""
        # 接收参数
        token = request.GET.get('token')

        # 校验参数：判断token是否为空和过期，提取user
        if not token:
            return http.HttpResponseBadRequest('缺少token')

        user = check_verify_email_token(token)
        if not user:
            return http.HttpResponseForbidden('无效的token')

        # 修改email_active的值为True
        try:
            user.email_active = True
            user.save()
        except Exception as e:
            logger.error(e)
            return http.HttpResponseServerError('激活邮件失败')

        # 返回邮箱验证结果
        return redirect(reverse('users:info'))

# 收货地址
class AddressView(LoginRequiredMixin, View):
    """用户收货地址"""

    def get(self, request):
        """提供收货地址界面"""
        # 获取用户地址列表（未删除的）
        login_user = request.user
        addresses = Address.objects.filter(user=login_user, is_deleted=False)
        # 转换前端的数据格式
        address_dict_list = []
        for address in addresses:
            address_dict = {
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,
                "city": address.city.name,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email
            }
            address_dict_list.append(address_dict)

        context = {
            'default_address_id': login_user.default_address_id,
            'addresses': address_dict_list,
        }
        # render---->jinja渲染----->js---->vue渲染
        return render(request, 'user_center_site.html', context)
# 新增收货地址
class CreateAddressView(LoginRequiredMixin, View):
    """新增地址"""

    def post(self, request):
        """实现新增地址逻辑"""
        # 判断是否超过地址上限：最多20个(用户的地址,没有逻辑删除的) .count > 20
        # Address.objects.filter(user=request.user, is_deleted=False).count()
        count = request.user.addresses.filter(is_deleted=False).count()
        if count >= constants.USER_ADDRESS_COUNTS_LIMIT:
            return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '超过地址数量上限'})

        # 接收参数  json json.loads(request.body.decode())[''](以下用get取不用[])
        json_dict = json.loads(request.body.decode())
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')

        # 校验参数 判空 判正则
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        # 保存地址信息  addresses.object.create()

        try:
            address = Address.objects.create(
                user=request.user,
                title=receiver,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )

            # 设置默认地址
            # 判断用户是否有默认收货地址
            if not request.user.default_address:
                request.user.default_address = address
                request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '新增地址失败'})

        # 新增地址成功，将新增的地址响应给前端实现局部刷新
        # 构建前端需要的字典
        address_dict = {
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,
            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        }

        # 响应保存结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '新增地址成功', 'address':address_dict})

#　修改密码
class ChangePasswordView(LoginRequiredMixin, View):
    """修改密码"""

    def get(self, request):
        """展示修改密码界面"""
        return render(request, 'user_center_pass.html')

    def post(self, request):
        """实现修改密码逻辑"""
        # 接收参数
        old_password = request.POST.get('old_pwd')
        new_password = request.POST.get('new_pwd')
        new_password2 = request.POST.get('new_cpwd')

        # 校验参数
        if not all([old_password, new_password, new_password2]):
            return http.HttpResponseForbidden('缺少必传参数')

        # 校验密码是否正确
        result = request.user.check_password(old_password)
        # result 为false 密码不正确
        if not result:

            return render(request, 'user_center_pass.html', {'origin_pwd_errmsg':'原始密码错误'})
        if not re.match(r'^[0-9A-Za-z]{8,20}$', new_password):
            return http.HttpResponseForbidden('密码最少8位，最长20位')
        if new_password != new_password2:
            return http.HttpResponseForbidden('两次输入的密码不一致')

        # 修改密码
        try:
            request.user.set_password(new_password)
            request.user.save()
        except Exception as e:
            logger.error(e)
            return render(request, 'user_center_pass.html', {'change_pwd_errmsg': '修改密码失败'})

        # 清理登录状态,登录页－－－清楚cookie
        logout(request)
        response = redirect(reverse('users:login'))
        response.delete_cookie('username')

        # # 响应密码修改结果：重定向到登录界面
        return response

#　用户浏览记录
class UserBrowseHistory(LoginRequiredMixin, View):
    """用户浏览记录"""
    # 新增
    def post(self, request):
        """保存用户浏览记录"""
        # 1.接收json参数
        sku_id = json.loads(request.body.decode()).get('sku_id')

        # 2.根据sku_id 查询sku（校验sku是否存在）
        try:
            sku = SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return http.HttpResponseForbidden('商品不存在!')
        # 链接redis服务
        # 3.如果有sku,保存到redis
        history_redis_client = get_redis_connection('history')
        history_key = 'history_%s' % request.user.id

        redis_pipeline = history_redis_client.pipeline()
        # 3.1 去重
        history_redis_client.lrem(history_key, 0, sku_id)
        # 3.2 存储
        history_redis_client.lpush(history_key, sku_id)
        # 3.3 截取 5个
        history_redis_client.ltrim(history_key, 0, 4)
        redis_pipeline.execute()

        # 响应结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})
    # 查询
    def get(self, request):
        """获取用户浏览记录"""
        # 获取Redis存储的sku_id列表信息
        redis_conn = get_redis_connection('history')
        sku_ids = redis_conn.lrange('history_%s' % request.user.id, 0, -1)

        # 根据sku_ids列表数据，查询出商品sku信息（遍历sku_ids--ＳＫＵ表查询）
        skus = []
        for sku_id in sku_ids:
            sku = SKU.objects.get(id=sku_id)
            #　转换前端需要的数据格式
            skus.append({
                'id': sku.id,
                'name': sku.name,
                'default_image_url': sku.default_image.url,
                'price': sku.price
            })

        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'skus': skus})


# 忘记密码页显示
class FindPasswordView(View):
    def get(self, request):
        return render(request, 'find_password.html')

# 忘记密码提交
class UserNewPasswordView(View):
    def post(self, request, user_id):
        # user_id(路径参数)　int　是　用户名id
        # password(json参数)　str　是　新密码
        # password2(json参数)　str　是　再次输入密码
        # access_token(json参数)　str　是　access_token(校验token值)
        # 接收参数校验参数
        json_dict = json.loads(request.body.decode())
        password = json_dict.get('password')
        password2 = json_dict.get('password2')
        access_token = json_dict.get('access_token')
        if not all([password, password2, access_token]):
            return http.HttpResponseForbidden('参数不能为空!')

            # 解密前端 传入的
        loads_acces_token = SecretOauth().loads(access_token)

        if not re.match('^[0-9A-Za-z]{8,20}$', password):
            return http.HttpResponseForbidden('请输入8-20位的密码')
        #　3.确认密码: ---------判空,判断是否相等
        if password2 != password:
            return http.HttpResponseForbidden('两次密码输入不一致')
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return http.JsonResponse({"status": 5002, 'message': "user_id有误!"})

        redis_client = get_redis_connection('verify_image_code')
        redis_random_token = redis_client.get('random_%s' % user.mobile)
        if loads_acces_token != redis_random_token.decode():
            return http.JsonResponse({"status": 5001, 'message': "token错误!"})

        # 更新密码
        user.set_password(password)
        user.save()

        return http.JsonResponse({"status": 5000, 'message': "密码设置成功!"})


# 忘记密码第二步
class FindSecondView(View):
    def get(self, request, mobile):
        sms_code = request.GET.get('sms_code')
        # 1.校验手机号
        try:
            user = User.objects.get(mobile=mobile)
        except User.DoesNotExist:
            return http.JsonResponse({"status": 5004})
            # 2.校验验证码
        sms_client = get_redis_connection('sms_code')
        redis_sms_code = sms_client.get('sms_%s' % mobile)
        if sms_code != redis_sms_code.decode():
            return http.JsonResponse({"status": 5001})
            # 3.返回正确的响应
        redis_client = get_redis_connection('verify_image_code')
        redis_random_token = redis_client.get('random_%s' % mobile)
        access_token = SecretOauth().dumps(redis_random_token.decode())

        return http.JsonResponse({"status": 5000, "user_id": user.id, "access_token": access_token})

# 忘记密码发送短信
class FindPasswordSendSmsCodeView(View):
    def get(self, request, mobile):
        # 1.接收access_token 解密 校验是否准确
        access_token = request.GET.get('access_token')
        # 解密前端 传入的
        loads_acces_token = SecretOauth().loads(access_token)

        # 获取后台存储的
        redis_client = get_redis_connection('verify_image_code')
        redis_random_token = redis_client.get('random_%s' % mobile)
        # 判断token是否一致
        if loads_acces_token != redis_random_token.decode():
            return http.JsonResponse({"status": 5001, 'message': "token错误!"})
        # 3.生成随机 6位 短信验证码内容 random.randit()
        from random import randint
        sms_code = '%06d' % randint(0, 999999)
        # 4.存储 随机6位 redis里面(3步 )
        sms_client = get_redis_connection('sms_code')

        # 1.获取 频繁发送短信的 标识
        send_flag = sms_client.get('send_flag_%s' % mobile)
        # 2.判断标识 是否存在
        if send_flag:
            return http.JsonResponse({'code': '4001', 'errmsg': '发送短信过于频繁66'})
        # 3.标识不存在 ,重新倒计时
        p1 = sms_client.pipeline()
        p1.setex('send_flag_%s' % mobile, 60, 1)
        p1.setex('sms_%s' % mobile, 300, sms_code)
        p1.execute()
        # 5.发短信---第三方容联云--
        print("原始短信:", sms_code)
        # from celery_tasks.sms.tasks import ccp_send_sms_code
        # ccp_send_sms_code.delay(mobile, sms_code)
        #

        # 6.返回响应对象
        return http.JsonResponse({"status": 200, 'message': "短信发送成功!"})
# 忘记密码第一步
class FindFirstView(View):
    def get(self, request, username):
        # 1.接收参数
        image_code = request.GET.get('image_code')
        uuid = request.GET.get('image_code_id')
        # 2.校验参数
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            return http.JsonResponse({'status': 5004})
        # 连接数据库　读取图片验证码　　与校验　
        img_client = get_redis_connection('verify_image_code')
        redis_img_code = img_client.get('img_%s' % uuid)
        if image_code.lower() != redis_img_code.decode().lower():
            return http.JsonResponse({'status': 5001})
        # 生成64位码字符串　存入redis 为下次发短信提交做准备
        mobile = user.mobile
        random_str = generat_csrf()
        img_client.setex('random_%s' % mobile, 300, random_str)

        access_token = SecretOauth().dumps(random_str)
        # 3.返回响应
        return http.JsonResponse({'status': 5000, "mobile": mobile, "access_token": access_token})
def generat_csrf():
    return bytes.decode(base64.b64encode(os.urandom(48)))